// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: linchuan

#pragma once

#include <memory>
#include <optional>
#include <regex>
#include <string>

#include "coin/proto/symbology.pb.h"
#include "coin2/exchange/base/symbology/spot_product.h"
#include "coin2/exchange/base/symbology/util.h"
#include "coin2/exchange/kraken/symbology/currency.h"

namespace coin2::exchange::kraken::symbology {

class KrakenProduct : public base::symbology::SpotProduct<KrakenCurrency, KrakenProduct>,
                      public base::symbology::ProductFromStrMixin<KrakenProduct> {
 public:
  using BaseProduct = SpotProduct<KrakenCurrency, KrakenProduct>;

  KrakenProduct(const KrakenCurrency& base, const KrakenCurrency& quote)
      : BaseProduct(
            base,
            quote,
            ToKrakenNativeProduct(base, quote),
            ToKrakenNativeSubscriptionSymbol(base, quote),
            base::market::ExchangeType::Kraken) {}

  static std::unique_ptr<KrakenProduct> FromNativeStrImpl(
      const std::string& native_product,
      int64_t resolve_timestamp) {
    std::unique_ptr<KrakenCurrency> base_ptr;
    std::unique_ptr<KrakenCurrency> quote_ptr;
    bool success =
        ParseKrakenNativeProduct(native_product, resolve_timestamp, &base_ptr, &quote_ptr);
    if (success) {
      return std::make_unique<KrakenProduct>(*base_ptr, *quote_ptr);
    } else {
      return {};
    }
  }

 private:
  static bool ParseKrakenNativeProduct(
      const std::string& product_str,
      int64_t resolve_timestamp,
      std::unique_ptr<KrakenCurrency>* base_ptr,
      std::unique_ptr<KrakenCurrency>* quote_ptr) {
    using base::market::MarketType;
    using base::symbology::HasValue;

    static const std::unordered_map<std::string, std::string> map = {
        {"USDTZUSD", "USDTUSD"},
        {"XETCXETH", "ETCETH"},
        {"XETCXXBT", "ETCXBT"},
        {"XETCZEUR", "ETCEUR"},
        {"XETCZUSD", "ETCUSD"},
        {"XETHXXBT", "ETHXBT"},
        {"XETHZCAD", "ETHCAD"},
        {"XETHZEUR", "ETHEUR"},
        {"XETHZGBP", "ETHGBP"},
        {"XETHZJPY", "ETHJPY"},
        {"XETHZUSD", "ETHUSD"},
        {"XLTCXXBT", "LTCXBT"},
        {"XLTCZEUR", "LTCEUR"},
        {"XLTCZJPY", "LTCJPY"},
        {"XLTCZUSD", "LTCUSD"},
        {"XMLNXETH", "MLNETH"},
        {"XMLNXXBT", "MLNXBT"},
        {"XMLNZEUR", "MLNEUR"},
        {"XMLNZUSD", "MLNUSD"},
        {"XREPXETH", "REPETH"},
        {"XREPXXBT", "REPXBT"},
        {"XREPZEUR", "REPEUR"},
        {"XREPZUSD", "REPUSD"},
        {"XXBTZCAD", "XBTCAD"},
        {"XXBTZEUR", "XBTEUR"},
        {"XXBTZGBP", "XBTGBP"},
        {"XXBTZJPY", "XBTJPY"},
        {"XXBTZUSD", "XBTUSD"},
        {"XXDGXXBT", "XDGXBT"},
        {"XXLMXXBT", "XLMXBT"},
        {"XXLMZAUD", "XLMAUD"},
        {"XXLMZEUR", "XLMEUR"},
        {"XXLMZGBP", "XLMGBP"},
        {"XXLMZUSD", "XLMUSD"},
        {"XXMRXXBT", "XMRXBT"},
        {"XXMRZEUR", "XMREUR"},
        {"XXMRZUSD", "XMRUSD"},
        {"XXRPXXBT", "XRPXBT"},
        {"XXRPZCAD", "XRPCAD"},
        {"XXRPZEUR", "XRPEUR"},
        {"XXRPZJPY", "XRPJPY"},
        {"XXRPZUSD", "XRPUSD"},
        {"XZECXXBT", "ZECXBT"},
        {"XZECZEUR", "ZECEUR"},
        {"XZECZUSD", "ZECUSD"},
        {"ZEURZUSD", "EURUSD"},
        {"ZGBPZUSD", "GBPUSD"},
        {"ZUSDZCAD", "USDCAD"},
        {"ZUSDZJPY", "USDJPY"},
    };

    static const std::regex reg("^([A-Z0-9.]{1,12})(CHF|DAI|DOT|USDC|USDT|ETH|XBT|AUD|CAD|EUR|GBP|JPY|USD|AED)$");

    std::string product = product_str;
    if (map.count(product_str) > 0) {
      product = map.at(product_str);
    }

    std::smatch match;
    bool res = std::regex_match(product, match, reg);
    if (!res) return false;

    auto ToCurrency = [](std::string currency) {
      if (extra_x.count(currency) > 0) {
        return std::string("X") + currency;
      }

      if (extra_z.count(currency) > 0) {
        return std::string("Z") + currency;
      }

      return currency;
    };

    std::string base = ToCurrency(match[1]);
    std::string quote = ToCurrency(match[2]);

    *base_ptr = KrakenCurrency::FromNativeStrNoThrow(base, resolve_timestamp);
    *quote_ptr = KrakenCurrency::FromNativeStrNoThrow(quote, resolve_timestamp);
    return true;
  }

  static std::string ToKrakenNativeProduct(
      const KrakenCurrency& base,
      const KrakenCurrency& quote) {
    static const std::unordered_map<std::string, std::string> map = {
        {"USDTUSD", "USDTZUSD"},
        {"ETCETH", "XETCXETH"},
        {"ETCXBT", "XETCXXBT"},
        {"ETCEUR", "XETCZEUR"},
        {"ETCUSD", "XETCZUSD"},
        {"ETHXBT", "XETHXXBT"},
        {"ETHCAD", "XETHZCAD"},
        {"ETHEUR", "XETHZEUR"},
        {"ETHGBP", "XETHZGBP"},
        {"ETHJPY", "XETHZJPY"},
        {"ETHUSD", "XETHZUSD"},
        {"LTCXBT", "XLTCXXBT"},
        {"LTCEUR", "XLTCZEUR"},
        {"LTCJPY", "XLTCZJPY"},
        {"LTCUSD", "XLTCZUSD"},
        {"MLNETH", "XMLNXETH"},
        {"MLNXBT", "XMLNXXBT"},
        {"MLNEUR", "XMLNZEUR"},
        {"MLNUSD", "XMLNZUSD"},
        {"REPETH", "XREPXETH"},
        {"REPXBT", "XREPXXBT"},
        {"REPEUR", "XREPZEUR"},
        {"REPUSD", "XREPZUSD"},
        {"XBTCAD", "XXBTZCAD"},
        {"XBTEUR", "XXBTZEUR"},
        {"XBTGBP", "XXBTZGBP"},
        {"XBTJPY", "XXBTZJPY"},
        {"XBTUSD", "XXBTZUSD"},
        {"XDGXBT", "XXDGXXBT"},
        {"XLMXBT", "XXLMXXBT"},
        {"XLMAUD", "XXLMZAUD"},
        {"XLMEUR", "XXLMZEUR"},
        {"XLMGBP", "XXLMZGBP"},
        {"XLMUSD", "XXLMZUSD"},
        {"XMRXBT", "XXMRXXBT"},
        {"XMREUR", "XXMRZEUR"},
        {"XMRUSD", "XXMRZUSD"},
        {"XRPXBT", "XXRPXXBT"},
        {"XRPCAD", "XXRPZCAD"},
        {"XRPEUR", "XXRPZEUR"},
        {"XRPJPY", "XXRPZJPY"},
        {"XRPUSD", "XXRPZUSD"},
        {"ZECXBT", "XZECXXBT"},
        {"ZECEUR", "XZECZEUR"},
        {"ZECUSD", "XZECZUSD"},
        {"EURUSD", "ZEURZUSD"},
        {"GBPUSD", "ZGBPZUSD"},
        {"USDCAD", "ZUSDZCAD"},
        {"USDJPY", "ZUSDZJPY"},
    };

    std::string native_base = base.native_currency();
    std::string native_quote = quote.native_currency();

    auto IsCurrencyExtraXZ = [](std::string currency) {
      if (currency.size() == 0) {
        return false;
      }

      std::unordered_set<std::string>* p_extra = nullptr;
      if (currency[0] == 'Z') {
        p_extra = &extra_z;
      }
      if (currency[0] == 'X') {
        p_extra = &extra_x;
      }

      bool res = false;
      if (p_extra) {
        res = any_of(p_extra->begin(), p_extra->end(), [currency](std::string extra) {
          if (currency.find(extra) != std::string::npos) {
            return true;
          }

          return false;
        });
      }

      return res;
    };

    bool b = IsCurrencyExtraXZ(native_base);
    bool q = IsCurrencyExtraXZ(native_quote);

    if (b == true) {
      native_base = native_base.substr(1, native_base.size() - 1);
    }

    if (q == true) {
      native_quote = native_quote.substr(1, native_quote.size() - 1);
    }

    std::string symbol = native_base + native_quote;
    if (map.count(symbol) > 0) {
      return map.at(symbol);
    } else {
      return symbol;
    }
  }

  static std::string ToKrakenNativeSubscriptionSymbol(
      const KrakenCurrency& base,
      const KrakenCurrency& quote) {
    std::stringstream ss;
    ss.str(std::string());
    ss << ToKrakenSubscriptionCurrency(base.currency()) << "/"
       << ToKrakenSubscriptionCurrency(quote.currency());
    return ss.str();
  }

  static std::string ToKrakenSubscriptionCurrency(const std::string& norm_currency) {
    if (norm_currency == "BTC") {
      return "XBT";
    } else if (norm_currency == "BCHABC" || norm_currency == "BCHN") {
      return "BCH";
    } else if (norm_currency == "DOGE") {
      return "XDG";
    } else if (norm_currency == "LUNC") {
      return "LUNA";
    }
    else {
      return norm_currency;
    }
  }

  static std::unordered_set<std::string> extra_x;
  static std::unordered_set<std::string> extra_z;
};

}  // namespace coin2::exchange::kraken::symbology
